chore: provide more scalable interface for dynamic checkout#250
chore: provide more scalable interface for dynamic checkout#250jakubjasinsky wants to merge 2 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR redesigns the public Dynamic Checkout initialization API by introducing a flatter initDynamicCheckout config shape with per-payment-method overrides, while keeping downstream checkout behavior stable via a normalization layer.
Changes:
- Added
initDynamicCheckout(new config types + normalizer) and deprecatedsetupDynamicCheckout. - Updated Dynamic Checkout internals to consume a normalized method-scoped config (options/theme/text/additionalData per method).
- Updated examples and docs UI to showcase both APIs and display a live “Code Example” preview.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/processout/processout.ts | Deprecates setupDynamicCheckout, adds initDynamicCheckout, and routes both through normalization. |
| src/dynamic-checkout/dynamic-checkout.ts | Updates DynamicCheckout constructor to accept normalized config; removes legacy theme plumbing. |
| src/dynamic-checkout/config/payment-config.ts | Introduces new public init config/types, normalization functions, and method-scoped runtime accessors. |
| src/dynamic-checkout/views/payment-methods.ts | Removes theme propagation when constructing payment method UI elements. |
| src/dynamic-checkout/payment-methods/card.ts | Switches card method to method-scoped options/theme/text overrides. |
| src/dynamic-checkout/payment-methods/apm.ts | Switches APM method to method-scoped options/theme/text overrides and additional data lookup. |
| src/dynamic-checkout/payment-methods/native-apm.ts | Switches native APM to method-scoped theme/text/options lookups. |
| src/dynamic-checkout/payment-methods/saved-card.ts | Switches saved card to method-scoped options (capture/fallback/status-message). |
| src/dynamic-checkout/payment-methods/saved-apm.ts | Switches saved APM to method-scoped options and method-scoped additional data lookup. |
| src/dynamic-checkout/clients/google-pay.ts | Applies method-scoped options for Google Pay payment flow. |
| src/dynamic-checkout/clients/apple-pay.ts | Applies method-scoped options for Apple Pay payment flow. |
| examples/dynamic-checkout/styles.css | Adds styling for the new code preview block. |
| examples/dynamic-checkout/index.html | Adds “Code Example” preview and updates example wiring for deprecated setup API. |
| examples/dynamic-checkout-init/index.html | Adds a new init-based example demonstrating the flat + override config pattern. |
| index.html | Adds a link to the new initDynamicCheckout example. |
| package.json | Bumps package version to 1.8.6. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (config.paymentMethodOverrides) { | ||
| for (const key in config.paymentMethodOverrides) { | ||
| const override = config.paymentMethodOverrides[key] | ||
|
|
||
| if (!override) { | ||
| continue | ||
| } | ||
|
|
||
| if (override.options) { | ||
| optionsByMethod[key] = { ...override.options } | ||
| } | ||
|
|
||
| if (override.theme) { | ||
| themeByMethod[key] = { ...override.theme } | ||
| } | ||
|
|
||
| if (override.text) { | ||
| textOverridesByMethod[key] = { ...override.text } | ||
| } | ||
|
|
||
| if (override.additionalData) { | ||
| additionalPaymentDataByMethod[key] = { ...override.additionalData } | ||
| } | ||
| } |
There was a problem hiding this comment.
normalizeDynamicCheckoutConfig copies paymentMethodOverrides keys into method-scoped maps using for...in without an own-property guard and without filtering reserved keys. Since override keys are arbitrary strings, this can again enable inherited key enumeration and prototype-pollution via keys like __proto__. Use Object.keys()/hasOwnProperty and reject __proto__/constructor/prototype (or build the maps with Object.create(null)).
| allow_fallback_to_sale: methodOptions.allowFallbackToSale, | ||
| save_source: canSavePaymentMethod && methodOptions.enforceSavePaymentMethod, |
There was a problem hiding this comment.
methodOptions comes from a merged config where option properties are optional; if a consumer passes an option key with an undefined value, spreading can overwrite the default boolean and make allowFallbackToSale/enforceSavePaymentMethod undefined. Here those values are forwarded directly into the payment request (allow_fallback_to_sale, save_source), which should be booleans. Either coerce to booleans at usage (consistent with other payment methods) or ensure the normalizer/merger drops undefined values so getOptionsForMethod always returns booleans.
| allow_fallback_to_sale: methodOptions.allowFallbackToSale, | |
| save_source: canSavePaymentMethod && methodOptions.enforceSavePaymentMethod, | |
| allow_fallback_to_sale: !!methodOptions.allowFallbackToSale, | |
| save_source: !!(canSavePaymentMethod && methodOptions.enforceSavePaymentMethod), |
| 'const client = new ProcessOut.ProcessOut("' + formValues.projectId + '")\n\n' + | ||
| "const checkout = client.setupDynamicCheckout(\n" + | ||
| " " + JSON.stringify(config, null, 2).replace(/\n/g, "\n ") + ",\n" + | ||
| " " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + ",\n" + |
There was a problem hiding this comment.
The generated code preview includes an extra trailing comma after the theme argument (...,\n)), which makes the displayed snippet invalid JavaScript for some environments. Drop the comma after the second argument so the preview matches a runnable setupDynamicCheckout(config, theme) call.
| " " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + ",\n" + | |
| " " + JSON.stringify(theme, null, 2).replace(/\n/g, "\n ") + "\n" + |
| name="enforceSavePaymentMethod" | ||
| type="checkbox" | ||
| /> | ||
| <span>Enforce Safe Payment Method</span> |
There was a problem hiding this comment.
Typo in the form label: it says "Enforce Safe Payment Method" but the config field is enforceSavePaymentMethod. Consider updating the label text to "Enforce Save Payment Method" (and, if you rename the input id, update the corresponding DOM lookup in this same file).
| <span>Enforce Safe Payment Method</span> | |
| <span>Enforce Save Payment Method</span> |
| const clonedConfig: DynamicCheckoutMethodScopedConfig<T> = {} | ||
|
|
||
| if (!config) { | ||
| return clonedConfig | ||
| } | ||
|
|
||
| for (const key in config) { | ||
| if (config[key]) { | ||
| clonedConfig[key] = { | ||
| ...config[key], |
There was a problem hiding this comment.
cloneMethodScopedConfig iterates with for...in and copies keys directly into a plain object. Because this config originates from user input, this pattern can enumerate inherited properties and allows special keys like __proto__/constructor to mutate the target object's prototype (prototype pollution). Prefer iterating over Object.keys(config) (or hasOwnProperty checks) and/or using Object.create(null) for the cloned container while explicitly rejecting dangerous keys.
| const clonedConfig: DynamicCheckoutMethodScopedConfig<T> = {} | |
| if (!config) { | |
| return clonedConfig | |
| } | |
| for (const key in config) { | |
| if (config[key]) { | |
| clonedConfig[key] = { | |
| ...config[key], | |
| const clonedConfig = Object.create(null) as DynamicCheckoutMethodScopedConfig<T> | |
| if (!config) { | |
| return clonedConfig | |
| } | |
| for (const key of Object.keys(config)) { | |
| // Explicitly skip dangerous keys to prevent prototype pollution | |
| if (key === '__proto__' || key === 'constructor' || key === 'prototype') { | |
| continue | |
| } | |
| const value = config[key] | |
| if (value) { | |
| clonedConfig[key] = { | |
| ...value, |
Summary
Redesign
initDynamicCheckoutpublic config interface to eliminate_globalnesting, improve naming, and support per-payment-method overrides via a flat-then-override pattern. DeprecatesetupDynamicCheckout.Changes
DynamicCheckoutInitConfigTypewith flat top-leveloptions,theme,textand apaymentMethodOverridesdictionary for per-method configDynamicCheckoutPaymentMethodKeyunion type for autocomplete on known method keys (card,applepay,googlepay) while allowing arbitrary gateway namesnormalizeDynamicCheckoutConfig()maps the flat public shape into the internal_global-basedDynamicCheckoutMethodScopedConfigstructure — zero changes to downstream payment method codesetupDynamicCheckoutmarked@deprecatedwith JSDocDynamicCheckoutPublicConfigType,DynamicCheckoutInitPublicConfigType) and their normalizers marked@deprecatedsetupDynamicCheckout, newdynamic-checkout-initexample showcasesinitDynamicCheckoutpayment-config.tswith clear section headers (internal helpers → shared types → current API → deprecated API → normalization → runtime class)Additional Context
paymentMethodsConfigurationpattern — simple cases stay flat, per-method overrides are opt-inDynamicCheckoutPaymentConfigclass internals unchangedtarget: "es5")